package httpclient

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"sync"
	"time"
)

const clientVersion = "0.0.2"

type ClientHttp struct {
	params      map[string]interface{}
	requestBody []byte
	header      http.Header
	contentType string
	errorRaw    string
	timeout     int
	mutex       sync.Mutex
	cooJar      *cookiejar.Jar

	cookieData map[string]*http.Cookie
}

type ClientResp struct {
	Body        []byte // response body
	BodyRaw     *http.Response
	RequestTime time.Duration
	CookieData  map[string]*http.Cookie
}

func (c *ClientResp) String() string {
	return string(c.Body)
}

func (c *ClientResp) GetRequestTime() time.Duration {
	return c.RequestTime
}

func NewClient() *ClientHttp {
	defaultHeader := make(http.Header)
	defaultHeader.Add("User-Agent", "OrangeClient/"+clientVersion)
	return &ClientHttp{
		contentType: "application/x-www-form-urlencoded",
		header:      defaultHeader,
		timeout:     10,
		errorRaw:    "",
		cookieData:  make(map[string]*http.Cookie),
	}
}

func (c *ClientHttp) Header(key, value string) *ClientHttp {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.header.Set(key, value)
	if strings.ToLower(key) == "content-type" {
		c.contentType = value
	}
	return c
}

func (c *ClientHttp) ContentType(value string) *ClientHttp {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.header.Set("Content-Type", value)
	c.contentType = value

	return c
}

// ResetParam 清空重置请求参数
func (c *ClientHttp) ResetParam() *ClientHttp {
	c.requestBody = []byte{}
	c.params = map[string]interface{}{}
	return c
}

// WithFormRequest 快速配置表单请求类型
func (c *ClientHttp) WithFormRequest() *ClientHttp {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.header.Set("Content-Type", "application/x-www-form-urlencoded")
	c.contentType = "application/x-www-form-urlencoded"

	return c
}

// WithFormRequest 快速配置表单请求类型
func (c *ClientHttp) WithJsonRequest() *ClientHttp {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.header.Set("Content-Type", "application/json")
	c.contentType = "application/json"

	return c
}

// RunGet 执行Get请求
func (c *ClientHttp) RunGet(clientUrl string) (clientResp *ClientResp, err error) {
	if c.errorRaw != "" {
		return nil, c.Error()
	}

	var defaultClient = &http.Client{}

	if c.cooJar != nil {
		defaultClient.Jar = c.cooJar
	}

	req, err := http.NewRequest("GET", clientUrl, nil)
	if err != nil {
		return nil, err
	}
	timeSt := time.Now()
	// 超时设置
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.timeout)*time.Second)
	defer cancel()
	req = req.WithContext(ctx)

	c.header.Set("Cookie", "")
	req.Header = c.header

	resp, err := defaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	for _, item := range resp.Cookies() {
		c.setCookieData(item.Name, item)
	}

	requestTime := time.Now().Sub(timeSt)
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body = ioutil.NopCloser(bytes.NewReader(body))
	clientResp = &ClientResp{
		BodyRaw:     resp,
		Body:        body,
		RequestTime: requestTime,
		CookieData:  c.cookieData,
	}

	return clientResp, err
}

// RunGet 执行Post请求
func (c *ClientHttp) RunPost(clientUrl string) (clientResp *ClientResp, err error) {
	if len(c.params) > 0 {
		c.paraseParams()
	}

	if c.errorRaw != "" {
		return nil, c.Error()
	}

	var defaultClient = &http.Client{}
	if c.cooJar != nil {
		defaultClient.Jar = c.cooJar
	}

	req, err := http.NewRequest("POST", clientUrl, bytes.NewBuffer(c.requestBody))
	if err != nil {
		return nil, err
	}

	timeSt := time.Now()
	// 超时设置
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.timeout)*time.Second)
	defer cancel()
	req = req.WithContext(ctx)
	c.header.Set("Cookie", "")
	req.Header = c.header
	req.Header.Set("Content-Type", c.contentType)

	resp, err := defaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	for _, item := range resp.Cookies() {
		c.setCookieData(item.Name, item)
	}

	requestTime := time.Now().Sub(timeSt)
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body = ioutil.NopCloser(bytes.NewReader(body))
	clientResp = &ClientResp{
		BodyRaw:     resp,
		Body:        body,
		RequestTime: requestTime,
		CookieData:  c.cookieData,
	}

	return clientResp, err
}

func (c *ClientHttp) SetTimeout(timeout int) *ClientHttp {
	c.timeout = timeout
	return c
}

// cookie保持 通过配置请求id将cookie保持
func (c *ClientHttp) WithCookie() *ClientHttp {
	if c.cooJar == nil {
		cooJar, _ := cookiejar.New(nil)
		c.cooJar = cooJar
	}
	return c
}

// 直接传递body中的参数
func (c *ClientHttp) WithBody(bodyStream string) *ClientHttp {
	c.requestBody = []byte(bodyStream)
	return c
}

// 参数设置 表单请求支持json和form两种类型
func (c *ClientHttp) FormParams(obj map[string]interface{}) *ClientHttp {
	c.params = obj
	return c
}

func (c *ClientHttp) paraseParams() *ClientHttp {
	if strings.Contains(c.contentType, "application/x-www-form-urlencoded") {
		params := ""
		val := make([]interface{}, 0, len(c.params))
		for k, item := range c.params {
			params += k + "=%v&"
			val = append(val, item)
		}
		paramsLen := len(params) - 1
		params = params[0:paramsLen]
		params = fmt.Sprintf(params, val...)
		c.requestBody = []byte(params)

		return c
	}

	if strings.Contains(c.contentType, "application/json") {
		params, err := json.Marshal(c.params)
		if err != nil {
			c.errorRaw += err.Error() + "|"
		}
		c.requestBody = params
		return c
	}
	c.errorRaw += "Use params you  must set  Content-Type eq 'application/json' or 'application/x-www-form-urlencoded'"

	return c
}

func (c *ClientHttp) Error() error {
	if c.errorRaw == "" {
		return nil
	}
	return errors.New(c.errorRaw)
}

func (c *ClientHttp) setCookieData(name string, cookie *http.Cookie) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.cookieData[name] = cookie

}
